import { KeyValues } from "./types"

interface FuncArgs<T> {
  data: T[]
  config?: {children?: string }
}

interface MapFuncArgs<T> extends FuncArgs<T> {
  cb: (node: T) => T
}

interface FindFuncArgs<T> extends FuncArgs<T> {
  cb: (node: T) => boolean
}

export const map = <T extends KeyValues<any>>(options: MapFuncArgs<T>) => {
  const results: T[] = []
  const { data, cb, config } = options || {}
  const { children = 'children' } = config || {}

  ;(function eachFn (data, parNode?: T) {
    data.forEach(item => {
      const childs = item[children]
      const newItem = cb(item)
      delete newItem[children]

      if (parNode) {
        if (parNode[children]) {
          (parNode[children] as T[]).push(newItem)
        } else {
          (parNode[children] as T[]) = [newItem]
        }
      } else {
        results.push(newItem)
      }

      childs && eachFn(childs, newItem)
    })
  })(data)

  return results
}

export const find = <T extends KeyValues<any>>(options: FindFuncArgs<T>) => {
  const { data, cb, config } = options
  const { children = 'children' } = config ?? {}
  
  return (function findFunc (data) {
    const waitCheck = []
    for (const item of data) {
      if (cb(item)) {
        return item
      }
      waitCheck.push(...(item[children] ?? []))
    }
    if (waitCheck.length) {
      return findFunc(waitCheck)
    }
  })(data)
}

export const flat = <T extends KeyValues<any>>(options: FuncArgs<T>) => {
  const { config } = options
  const { children = 'children' } = config ?? {}
  return (function reduce (data): T[] {
    return data.reduce((t, _) => {
      return [...t, _, ...reduce(_[children] ?? [])]
    }, [] as T[])
  })(options.data)
}

export const findRelatedNodes = <T extends KeyValues<any>>(options: FindFuncArgs<T>) => {
  const { config } = options
  const { children = 'children' } = config ?? {}
  const node = find(options)
  if (node) {
    const results = [node]
    return (function findFunc (node) {
      const parNode = find({
        ...options,
        cb: _ => (_[children] as T[])?.includes(node)
      })
      if (parNode) {
        results.unshift(parNode)
        return findFunc(parNode)
      }
      return results
    })(node)
  }
  return []
}
